package scala.examples.tcpoly.calculi.lambda.simplified 

// the top-level classes are components, 
// in `class C { x: T => ... }`, the type (T) of the self variable (x) denotes C's required components
// see Odersky et al., Scalable Component Abstractions
// to avoid this "complexity" (uhm, modularity), you can just make all classes top-level
trait Syntax { self: Syntax with Binding  =>
  trait Term extends Nominal[Term] {
    def isValue: Boolean
  }

  case class Var(name: Name) extends Term {
    def isValue = false

    def swap(a: Name, b: Name) = Var(name swap(a, b))
    def fresh(a: Name) = name fresh(a)
    def alphaEq(o: AlsoNominal[Term]) = o match {
      case Var(n) => n.alphaEq(name)
      case _ => false
    }
  }

  case class Abs(abs: \\[Term]) extends Term {
    def isValue = true

    def swap(a: Name, b: Name) =  Abs(abs swap(a, b))
    def fresh(a: Name) = abs fresh(a)
    def alphaEq(o: AlsoNominal[Term]) = o match {
      case Abs(a) => a.alphaEq(abs)
      case _ => false
    }    
  }

  case class App(fun: Term, arg: Term) extends Term {
    def isValue = false

    def swap(a: Name, b: Name) = App(fun swap(a, b), arg  swap(a, b))
    def fresh(a: Name) = fun.fresh(a) && arg.fresh(a)
    def alphaEq(o: AlsoNominal[Term]) = o match {
      case App(f, a) => f.alphaEq(fun) && a.alphaEq(arg)
      case _ => false
    }    
  }

  class Literal extends Term { 
    def isValue = true 
    def swap(a: Name, b: Name) = this
    def fresh(a: Name) = true    
    def alphaEq(o: AlsoNominal[Term]) = o == this
  }
  case object t extends Literal
  case object f extends Literal
}

trait Binding { self: Binding => 
  type AlsoNominal[T] = T with Nominal[T] // avoid F-bounds
  trait Nominal[Self] { // something that contains names
    // return this entity after swapping a and b
    def swap(a: Name, b: Name): AlsoNominal[Self]

    // a is in the set of free variables of this entity
    def fresh(a: Name): Boolean
    
    def alphaEq(other: AlsoNominal[Self]): Boolean
  }
  
  object Name {
    private[Name] var nextTag: Int = -1 // only for toString
    def apply(name: String) = new Name(name)
    def apply(name: Name) = new Name(name.name)
  }
  class Name(val name: String) extends Nominal[Name] {
    def swap(a: Name, b: Name) : AlsoNominal[Name]
      = if(this.alphaEq(a)) b else if(this.alphaEq(b)) a else this

    def fresh(a: Name) = ! this.alphaEq(a)  

    def alphaEq(other: AlsoNominal[Name]) = other eq this // identity

    override def toString() = name+"$"+tag
    val tag = {Name.nextTag += 1; Name.nextTag} // only used in toString
  }
  
  case class \\[T](private val binder: Name,
              private val body: AlsoNominal[T]) extends Nominal[\\[T]] {

    def swap(a: Name, b: Name) 
      = new \\(binder swap(a, b), body swap(a, b))

    def fresh(a: Name) 
      = if(a.alphaEq(binder)) true  // implicitly alpha-convert binder
        else body.fresh(a)

    def alphaEq(o: AlsoNominal[\\[T]]): Boolean = (binder.alphaEq(o.binder) && body.alphaEq(o.body)) || 
        ((!binder.alphaEq(o.binder)) && 
         o.body.fresh(binder) && o.body.swap(o.binder, binder).alphaEq(body))

    def unabs: (Name, AlsoNominal[T]) = {
      val freshBinder = new Name(binder.name)    // Name(binder.name) would work too, this calls Name.apply(binder.name) (see object Name)
      (freshBinder, body.swap(binder, freshBinder))
    }
    
    override def toString() = {
      val (x, b) = unabs
      x+" \\ "+b
    }
    
  }  
}

trait Substitution { s: Syntax with Binding with Substitution =>
  def subst(self: Term, n: Name, to: Term): Term = self match{
    case Var(name) => if(name.alphaEq(n)) to else self
    case App(fun, arg) => App(subst(fun, n, to), 
                            subst(arg, n, to))
    case Abs(a) =>
      val (x, body) = a.unabs
      Abs(new \\(x, subst(body, n, to)))
    case _ : Literal => self
  }
}

trait Evaluation { self: Syntax with Substitution with Binding with Evaluation =>
  def eval(tm: Term): Term = tm match {
    case App(Abs(a), v) if v.isValue => 
      val (x, t) = a.unabs
      subst(t, x, v)
    case App(v, t) if v.isValue => App(v, eval(t))
    case App(t1, t2) => App(eval(t1), t2)
  }
  
  // transitive closure
  def evalX(tm: Term): Term = tm match {
    case v if v.isValue => v
    case tm => evalX(eval(tm)) // TODO: check this is tail-recursive
  }
}

object EvalTest extends Syntax with Substitution with Binding with Evaluation with Application {
  val y = Name("y")
  val x = Name("x")
  val y2 = Name("y")
  
  // (\y. (\x. \y. x) y) t
  val a = App(Abs(\\(y, App(Abs(\\(x, Abs(\\(y2, Var(x))))), Var(y)))), t)
//  val a = ('y -> (('x -> ('x -> 'x)) ~ 'y)) ~ t
//  val a = ('y -> (('x -> ('x -> 'x)) ~ 'y)) ~ t
//  val b = ('y -> ('x -> ('y -> 'x)) ~ 'y) ~ t
  
  println(evalX(a))
//  println(evalX(b))  
}

